﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection;
using TomanuExtensions;
using System.Text.RegularExpressions;
using System.Globalization;

namespace RaytracerLib.MathLib.PolynomialFunction
{
    public static class StringToRPN
    {
        private static Dictionary<Regex, Func<string, Element>> s_rpn_patterns =
            new Dictionary<System.Text.RegularExpressions.Regex, Func<string, Element>>()
        {
            { new Regex("^\\+"), (str) => new AddOperator() }, 
            { new Regex("^\\)"), (str) => new CloseBracketOperator() }, 
            { new Regex("^\\/"), (str) => new DivOperator() }, 
            { new Regex("^\\*"), (str) => new MulOperator() }, 
            { new Regex("^(([0-9]+\\.?[0-9]*)|([0-9]*\\.?[0-9]+))(e[-+]?[0-9]+)?"), 
                (str) => new NumberSymbol(Double.Parse(str, CultureInfo.InvariantCulture)) }, 
            { new Regex("^\\("), (str) => new OpenBracketOperator() }, 
            { new Regex("^\\^"), (str) => new PowOperator() }, 
            { new Regex("^\\-"), (str) => new SubOperator() }, 
            { new Regex("^(x|y|z)"), (str) => new VariableSymbol(str) }, 
        };

        public static IEnumerable<Element> Parse(string a_function)
        {
            a_function = PrepareFunctionString(a_function);
            List<Element> elements = ParseToArray(a_function);
            InsertMuls(elements);
            ReducePlusesMinuses(elements);
            ConvertPlusesMinusesToUnary(elements);
            RemoveUnaryPluses(elements);
            return ReorderToRPN(elements);
        }

        private static string PrepareFunctionString(string a_function)
        {
            a_function = a_function.Replace(" ", "");
            a_function = a_function.Replace("\t", "");
            a_function = a_function.Replace(",", ".");
            a_function = a_function.ToLower(CultureInfo.InvariantCulture);

            if (a_function.Contains('='))
            {
                int index = a_function.IndexOf('=');
                string right = a_function.Substring(index + 1);
                string left = a_function.Left(index);
                a_function = String.Format("{0}-({1})", left, right);
            }

            if (a_function.Contains('='))
                throw new FormatException();

            return a_function;
        }

        private static List<Element> ParseToArray(string a_function)
        {
            List<Element> result = new List<Element>();

            for (; ; )
            {
                bool finded = false;

                foreach (var rpn_pattern in s_rpn_patterns.Keys)
                {
                    if (rpn_pattern.IsMatch(a_function))
                    {
                        string rpn_string = rpn_pattern.Match(a_function).Captures[0].Value;
                        a_function = rpn_pattern.Replace(a_function, "");

                        var creator = s_rpn_patterns[rpn_pattern];
                        Element el = creator(rpn_string);
                        result.Add(el);

                        finded = true;
                        break;
                    }
                }

                if (!finded)
                    break;
            }

            if (a_function.Length != 0)
                throw new FormatException();

            return result;
        }

        private static void InsertMuls(List<Element> a_elements)
        {
            for (int i = a_elements.Count - 2; i >= 0; i--)
            {
                if (a_elements[i] is CloseBracketOperator)
                {
                    if (a_elements[i + 1] is SymbolElement)
                        a_elements.Insert(i + 1, new MulOperator(null, null));

                    if (a_elements[i + 1] is OpenBracketOperator)
                        a_elements.Insert(i + 1, new MulOperator(null, null));
                }

                if (a_elements[i] is SymbolElement)
                {
                    if (a_elements[i + 1] is SymbolElement)
                        a_elements.Insert(i + 1, new MulOperator(null, null));

                    if (a_elements[i + 1] is OpenBracketOperator)
                        a_elements.Insert(i + 1, new MulOperator(null, null));
                }
            }
        }

        private static void ReducePlusesMinuses(List<Element> a_elements)
        {
            for (; ; )
            {
                bool changed = false;

                for (int i = 0; i < a_elements.Count - 1; i++)
                {
                    Element reduced = null;

                    if (a_elements[i] is AddOperator)
                    {
                        if (a_elements[i + 1] is AddOperator)
                            reduced = new AddOperator(null, null);

                        if (a_elements[i + 1] is SubOperator)
                            reduced = new SubOperator(null, null);
                    }

                    if (a_elements[i] is SubOperator)
                    {
                        if (a_elements[i + 1] is AddOperator)
                            reduced = new SubOperator(null, null);

                        if (a_elements[i + 1] is SubOperator)
                            reduced = new AddOperator(null, null);
                    }

                    if (reduced != null)
                    {
                        a_elements.RemoveAt(i + 1);
                        a_elements.RemoveAt(i);
                        a_elements.Insert(i, reduced);
                        changed = true;
                        break;
                    }
                }

                if (!changed)
                    break;
            }
        }

        private static void ConvertPlusesMinusesToUnary(List<Element> a_elements)
        {
            for (int i = 0; i < a_elements.Count - 1; i++)
            {
                if (!(a_elements[i + 1] is SymbolElement))
                    continue;

                if (i > 0)
                {
                    Element prev = a_elements[i - 1];

                    if (!(prev is OpenBracketOperator) && !(prev is MulOperator) &&
                        !(prev is DivOperator) && !(prev is PowOperator))
                    {
                        continue;
                    }

                    if ((a_elements[i] is AddOperator))
                        a_elements[i] = new PlusOperator(null);

                    if ((a_elements[i] is SubOperator))
                        a_elements[i] = new MinusOperator(null);
                }
                else
                {
                    if ((a_elements[i] is AddOperator))
                        a_elements[i] = new PlusOperator(null);

                    if ((a_elements[i] is SubOperator))
                        a_elements[i] = new MinusOperator(null);
                }
            }
        }

        private static void RemoveUnaryPluses(List<Element> a_elements)
        {
            for (int i = a_elements.Count - 1; i >= 0; i--)
            {
                if (a_elements[i] is PlusOperator)
                    a_elements.RemoveAt(i);
            }
        }

        private static IEnumerable<Element> ReorderToRPN(List<Element> a_elements)
        {
            a_elements = a_elements.ToList();
            Stack<Element> stack = new Stack<Element>();
            List<Element> result = new List<Element>();

            while (a_elements.Count != 0) 
            {
                Element element = a_elements.RemoveFirst();
            
                if (element is SymbolElement) 
                {
                    result.Add(element);
                }
                else if (element is BinaryOperator) 
                {
                    if ((element is AddOperator) || (element is SubOperator))
                    {
                        while (stack.Any() && (stack.Peek() is UnaryOperator))
                            result.Add(stack.Pop());
                    }

                    while (stack.Any() && (stack.Peek() is BinaryOperator) && 
                           ((stack.Peek() as BinaryOperator).Priority >=
                           (element as BinaryOperator).Priority))
                    {
                        result.Add(stack.Pop());

                        if (stack.Any() && (stack.Peek() is MinusOperator))
                        {
                            result.Add(stack.Pop());
                            break;
                        }
                    }
                
                    stack.Push(element);
                } 
                else if (element is UnaryOperator)
                    stack.Push(element);
                else if (element is OpenBracketOperator) 
                    stack.Push(element);
                else if (element is CloseBracketOperator)
                {
                    while (!(stack.Peek() is OpenBracketOperator))
                        result.Add(stack.Pop());

                    stack.Pop(); 
                } 
                else 
                    throw new FormatException();
            }
        
            while (stack.Count > 0)
                result.Add(stack.Pop());

            return result;
        }
    }
}
